iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
Modern Web

30天React練功坊-攻克常見實務/面試問題系列 第 25

30天React練功坊-攻克常見實務/面試問題 Day25: Implement simple pagination(interview question)

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

我們昨天看了一個很水的custom hook題目,希望透過那個水到爆炸的題目你有了解到可以利用custom hook做類似的邏輯封裝提高你程式碼的可讀性與複用性,同時理解這並沒有這麼困難,不要被它嚇到了。今天我們來看一個稍稍複雜一些些的題目吧!

本日題目

請你觀察這個codesandbox以及下方的starter code。

import React from "react";
import { fetchUsers } from "./api.js";

function App() {
  return (
    <div>
      <h1>Implement Simple Pagination</h1>
      <h2>User List</h2>
      <button>Previous</button>
      <button>Next</button>
    </div>
  );
}

export default App;

這次的題目要你實作一個簡單的pagination,如下方的gif所示。

day25-demo-gif

題目已提供一個模擬api供你使用,fetchUsers函數會接受一個page做為參數,並回傳對應頁面的使用者資料以及是否有更多資料讓前端渲染。

// Mock API function
const fetchUsers = (page) => {
  const allUsers = [
    { id: 1, name: "Alice", age: 28 },
    { id: 2, name: "Bob", age: 24 },
    { id: 3, name: "Charlie", age: 22 },
    { id: 4, name: "David", age: 35 },
    { id: 5, name: "Eva", age: 29 },
    { id: 6, name: "Frank", age: 41 },
    { id: 7, name: "Grace", age: 38 },
    { id: 8, name: "Hannah", age: 21 },
    { id: 9, name: "Ivy", age: 33 },
    { id: 10, name: "Jack", age: 30 },
    { id: 11, name: "Katie", age: 27 },
    { id: 12, name: "Liam", age: 32 },
    { id: 13, name: "Mandy", age: 26 },
    { id: 14, name: "Nancy", age: 19 },
    { id: 15, name: "Oscar", age: 45 },
    { id: 16, name: "Paul", age: 55 },
    { id: 17, name: "Quincy", age: 40 },
    { id: 18, name: "Rachel", age: 23 },
    { id: 19, name: "Steve", age: 34 },
    { id: 20, name: "Tina", age: 20 }
  ];

  const startIdx = (page - 1) * 5;
  const endIdx = startIdx + 5;

  return new Promise((resolve) => {
    setTimeout(() => {
      const hasMore = endIdx < allUsers.length;
      resolve({ users: allUsers.slice(startIdx, endIdx), hasMore });
    }, 1000);
  });
};

export { fetchUsers };

請觀察上方提供的程式碼以及gif檔案,並根據下方的要求完成題目

1. 請勿更動fetchUsers函數
2. 呈現的畫面應與gif一致,fetch資料時需要出現Loading字樣
3. Next & Previous按鈕需順利獲得下一頁/前一頁的使用者資料,且滿足特定條件時應disable對應的按鈕(第一頁不該能點擊Previous、最後一頁不該能點擊Next)

解答與基本解釋

又是一個實務常見的需求,這類情境很容易出現在大量資料呈現的情況,為了避免一口氣渲染大量資料造成使用者體驗不佳,pagination或是infinite scrolling往往就是很直覺的解法,今天題目要求的內容很簡單,我們只要順利的渲染每一頁拿到的使用者資料,並根據api回傳的hasMore變數來判斷是否有更多資料可以fetch來決定要不要disable下一頁的按鈕。

首先觀察一下幾個點,這會關係到我們需要哪些state來控制我們的渲染行為

  1. users,用來渲染使用者名字
  2. page,用來決定現在要fetch第幾頁的資料
  3. loading,用來決定是否要展示Loading字樣
  4. hasMore,用來決定是否要繼續fetch資料

因此第一步你會需要引入這四個state管理,並完成基本的render結構,坦白講做到這邊就已經快要結束了。

function App() {
  const [users, setUsers] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
    
  return (
    <div>
      <h1>Implement Simple Pagination</h1>
      <h2>User List</h2>
      {loading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
      <button>
        Previous
      </button>
      <button>
        Next
      </button>
    </div>
  );
    
    
}    

剩下的部分則是需要觀察一下api的回傳值,我們看一下api.js檔案我們會發現最後的resolve value如下。

resolve({ users: allUsers.slice(startIdx, endIdx), hasMore });

其中你並不需要去在乎allUsers跟startIdx & endIdx這些鬼玩意,你只要知道最終你會拿到users & hasMore這兩個欄位,這麼一來你就可以在請求後知道怎麼去設那些state了,加入請求資料的useEffect後我們就可以根據page的變化去請求對應的資料。

useEffect(() => { // 加入請求資料的effect
  setLoading(true);
  fetchUsers(page).then((data) => {
    setUsers(data.users);
    setLoading(false);
    setHasMore(data.hasMore);
  });
}, [page]);

到這邊之後就剩最後一步了,我們透過demo可以知道真正決定資料請求的邏輯其實是綁在兩個按鈕上,因此我們需要在兩個按鈕上掛上對應的onClick handler,點擊時需要根據情況更新page值讓上方的useEffect知道要重新fetch新的資料,同時你也要加入正確的邏輯讓按鈕在特定情況下是disabled的,最終完整的程式碼如下。

import React, { useState, useEffect } from "react";
import { fetchUsers } from "./api.js";

function App() {
  const [users, setUsers] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  useEffect(() => { // 加入請求資料的effect
    setLoading(true);
    fetchUsers(page).then((data) => {
      setUsers(data.users);
      setLoading(false);
      setHasMore(data.hasMore);
    });
  }, [page]);

  return (
    <div>
      <h1>Implement Simple Pagination</h1>
      <h2>User List</h2>
      {loading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
      <button disabled={page === 1} onClick={() => setPage(page - 1)}>
        Previous
      </button>
      <button disabled={!hasMore} onClick={() => setPage(page + 1)}>
        Next
      </button>
    </div>
  );
}

export default App;

總結

今天我們看了一個簡單的pagination實作,雖然實務上的實作往往會包含更多的邏輯進去,但這個示範應該足夠讓你應付jr面試等級的問題了,做法上稱不上多困難,只要你有好好觀察題目的要求與提供的api,你大致上會很清楚需要哪一些state來達到你要的效果! 剩下最後五天了,加把勁吧! 我們明天見!

本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!


上一篇
2023It 30天React練功坊-攻克常見實務/面試問題 Day24: Creating a custom hook for data fetching(interview question)
下一篇
30天React練功坊-攻克常見實務/面試問題 Day26: Add emojis to the page onclick(interview question)
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言